#include "OS.h"
#include "OSSocket.h"

#pragma comment(lib, "ws2_32.lib")

using namespace host;

OSErr OSSocket::Open(int theType)
{
    hFileDesc = ::socket(AF_INET, theType, 0);
    if (hFileDesc < 0)
    {
        return (OSErr)OS::GetErrno();
    }

    return OSErr::OS_NoErr;
}

OSErr OSSocket::Close()
{
    ::closesocket(hFileDesc);

    return OSErr::OS_NoErr;
}

OSErr TCPSocket::Connect(UInt32 inRemoteAddr, UInt16 inRemotePort)
{
    ::memset(&m_tPeerAddr, 0, sizeof(m_tPeerAddr));
    m_tPeerAddr.sin_family = AF_INET;        /* host byte order */
    m_tPeerAddr.sin_port = htons(inRemotePort); /* short, network byte order */
    m_tPeerAddr.sin_addr.s_addr = htonl(inRemoteAddr);

    /* don't forget to error check the connect()! */
    int err = ::connect(hFileDesc, (sockaddr *)&m_tPeerAddr, sizeof(m_tPeerAddr));
    if (err == -1)
    {
        m_tPeerAddr.sin_port = 0;
        m_tPeerAddr.sin_addr.s_addr = 0;
        return (OSErr)OS::GetErrno();
    }

    return OSErr::OS_NoErr;
}

OSErr TCPSocket::Send(const void* inBuffer, size_t inLength, size_t* outLengthSent)
{
    if (hFileDesc)
    {
        return (OSErr)ENODEV;
    }

    auto retval = ::send(hFileDesc, (const char*)inBuffer, inLength, 0);//flags??
    if (retval <= 0)
    {
        return (OSErr)OS::GetErrno();
    }

    if (outLengthSent != NULL)
        *outLengthSent = (UInt32)retval;

    return OSErr::OS_NoErr;
}

OSErr TCPSocket::Recv(void* buffer, size_t inLengthMax, size_t* recvLen)
{
    if (hFileDesc)
    {
        return (OSErr)ENODEV;
    }

    auto theRecvLen = ::recv(hFileDesc, (char*)buffer, inLengthMax, 0);
    if (theRecvLen < 0)
        return (OSErr)OS::GetErrno();

    if(recvLen)
        *recvLen = (UInt32)theRecvLen;

    return OSErr::OS_NoErr;
}

//OSErr UDPSocket::Connect(UInt32 inRemoteAddr, UInt16 inRemotePort)
//{
//    ::memset(&m_tPeerAddr, 0, sizeof(m_tPeerAddr));
//    m_tPeerAddr.sin_family = AF_INET;        /* host byte order */
//    m_tPeerAddr.sin_port = htons(inRemotePort); /* short, network byte order */
//    m_tPeerAddr.sin_addr.s_addr = htonl(inRemoteAddr);
//
//    return OSErr::OS_NoErr;
//}

OSErr UDPSocket::SendTo(UInt32 inRemoteAddr, UInt16 inRemotePort, void* inBuffer, size_t inLength)
{
    assert(inBuffer != NULL);

    struct sockaddr_in  theRemoteAddr;
    theRemoteAddr.sin_family = AF_INET;
    theRemoteAddr.sin_port = htons(inRemotePort);
    theRemoteAddr.sin_addr.s_addr = htonl(inRemoteAddr);

    // Win32 says that inBuffer is a char*
    auto theErr = ::sendto(hFileDesc, (char*)inBuffer, inLength, 0, (sockaddr*)&theRemoteAddr, sizeof(theRemoteAddr));

    if (theErr == -1)
        return (OSErr)OS::GetErrno();

    return OSErr::OS_NoErr;
}

OSErr UDPSocket::RecvFrom(UInt32* outRemoteAddr, UInt16* outRemotePort, void* ioBuffer, size_t inBufLen, size_t* outRecvLen)
{
    assert(outRecvLen != NULL);
    assert(outRemoteAddr != NULL);
    assert(outRemotePort != NULL);

    struct sockaddr_in  theRemoteAddr;
    int addrLen = sizeof(theRemoteAddr);

    // Win32 says that ioBuffer is a char*
    auto theRecvLen = ::recvfrom(hFileDesc, (char*)ioBuffer, inBufLen, 0, (sockaddr*)&theRemoteAddr, &addrLen);
    if (theRecvLen == -1)
    {
        return (OSErr)OS::GetErrno();
    }

    assert(theRecvLen >= 0);
    *outRemoteAddr = ntohl(theRemoteAddr.sin_addr.s_addr);
    *outRemotePort = ntohs(theRemoteAddr.sin_port);
    *outRecvLen = (UInt32)theRecvLen;

    return OSErr::OS_NoErr;
}

OSErr UDPSocket::Send(const void* inBuffer, size_t inLength, size_t* outLengthSent)
{
    assert(inBuffer != NULL);

    // Win32 says that inBuffer is a char*
    auto theErr = ::sendto(hFileDesc, (char*)inBuffer, inLength, 0, (sockaddr*)&m_tPeerAddr, sizeof(m_tPeerAddr));
    if (theErr == -1)
    {
        return (OSErr)OS::GetErrno();
    }

    return OSErr::OS_NoErr;
}

OSErr UDPSocket::Recv(void* ioBuffer, size_t inBufLen, size_t* outRecvLen)
{
    UInt32 outRemoteAddr;
    UInt16 outRemotePort;

    return RecvFrom(&outRemoteAddr, &outRemotePort, ioBuffer, inBufLen, outRecvLen);
}

OSErr UDPSocket::Bind(UInt32 inLocalAddr, UInt16 inLocalPort)
{

    ::memset(&m_tSelfAddr, 0, sizeof(m_tSelfAddr));
    m_tSelfAddr.sin_family = AF_INET;
    m_tSelfAddr.sin_port = htons(inLocalPort);
    m_tSelfAddr.sin_addr.s_addr = htonl(inLocalAddr);

    int err = ::bind(hFileDesc, (sockaddr*)&m_tSelfAddr, sizeof(m_tSelfAddr));
    if (err == -1)
    {
        m_tSelfAddr.sin_port = 0;
        m_tSelfAddr.sin_addr.s_addr = 0;
        return (OSErr)OS::GetErrno();
    }

    return OSErr::OS_NoErr;
}
